﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Cofe.Core;
using Cofe.Core.Interfaces;
using Cofe.Core.Property;
using Cofe.Core.Service;
using Cofe.Core.Utils;
using Cofe.IO.Interfaces;
using CofeCore = Cofe.Core;
using CofeIO = Cofe.IO;

namespace Cofe.IO.Compress
{
    /// <summary>
    /// 
    /// </summary>
    public class CompressPropertyProvider : PropertyProviderBase<IPropertyHost>, IFSIPropertyProvider
    {
        [Export(typeof(ICofeService))]
        [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFEIO)]
        public class Factory : ISecondaryPropertyProviderFactory
        {
            static Factory()
            {
            }

            public IEnumerable<IPropertyProvider> GetSecoondaryPropertyProviders(IPropertyHost propertyHost)
            {
                if (_wrappers == null)
                    _wrappers = ServiceLocater.FindAllServices<ICompressorWrapper>().ToArray();

                if (!(propertyHost.AllPropertyProviders.Any(p => p is CompressPropertyProvider)))
                {
                    if (propertyHost.SupportedDefinitions.IsPropertySupported(CofeProperties.Type))
                        foreach (var wrapper in _wrappers)
                        {
                            string type = propertyHost.Behaviors.GetProperty(CofeProperties.Type).ValueAsString;
                            switch (type)
                            {
                                case FileTypes.File:
                                    string fileName = propertyHost.Behaviors.GetProperty(CofeProperties.FullParseName).ValueAsString;

                                    if (wrapper.IsArchive(fileName))
                                        yield return new CompressPropertyProvider(wrapper, propertyHost, entryType.etRoot);
                                    break;
                                case FileTypes.Folder:
                                    if (propertyHost.Behaviors.IsPropertySupported(CofeDirectoryProperties.CreateAsync))
                                        yield return new CompressPropertyProvider(wrapper, propertyHost, entryType.etDirectory);
                                    break;

                            }
                        }
                }
                yield break;
            }

            private IServiceLocater _serviceLocater = null;
            private ICompressorWrapper[] _wrappers = null;

            public IServiceLocater ServiceLocater
            {
                get { return _serviceLocater ?? CofeServices.ServiceLocater; }
                set { _serviceLocater = value; }
            }
        }

        #region Constructor

        private enum entryType { etDirectory, etRoot, etSubFolder, etFile }

        /// <summary>
        /// This is secondary property provider for archive root.
        /// </summary>
        /// <param name="rootPropertyHost"></param>
        private CompressPropertyProvider(ICompressorWrapper wrapper, IPropertyHost rootPropertyHost, entryType etype)
            : base(rootPropertyHost,
            Cofe.Core.PropertyDefinitions.CofeDirDefinitions,
            Cofe.IO.PropertyDefinitions.FSIPropDefinitions,
            Cofe.IO.PropertyDefinitions.DIPropDefinitions)
        {
            _wrapper = wrapper;
            _entryType = etype;
            if (etype != entryType.etRoot && etype != entryType.etDirectory)
                throw new ArgumentException("EntryType");

            if (etype == entryType.etRoot) //an archive.
            {
                _relativePath = "";
                _currentPropertyProvider = Entity.PrimaryPropertyProvider;
                initFolderBasedAction();
            }
            string parseName = rootPropertyHost.Behaviors.GetProperty<string>(CofeProperties.FullParseName);
            registerActionAsync<IPropertyProvider>(CofeDirectoryProperties.CreateAsync, 
                (pm) => createArchiveAsync(pm, etype != entryType.etDirectory));
        }

        /// <summary>
        /// This is primary property provider for archive contents, for listing existing folders or file.
        /// </summary>
        /// <param name="wrapper"></param>
        /// <param name="rootPropertyHost"></param>
        /// <param name="parentPropertyProvider"></param>
        /// <param name="pps"></param>
        internal CompressPropertyProvider(ICompressorWrapper wrapper, IPropertyHost rootPropertyHost,
            IPropertyProvider parentPropertyProvider, List<PropertyPair> pps)
            : this(wrapper, rootPropertyHost, parentPropertyProvider,
            pps.First(pp => pp.Property.Equals(CofeProperties.DefaultInterfaceType)).ValueAs<Type>().Equals(typeof(IDirectoryInfo)),
            pps.First(pp => pp.Property.Equals(FileSystemInfoProperties.RelativePath)).ValueAsString)
        {
            foreach (var pp in pps)
            {
                object val = pp.Value;
                registerProperties(pp.Property, () => val);
            }
        }

        /// <summary>
        /// This is primary property provider for archive contents, for constructing new folders or file.
        /// </summary>
        /// <param name="rootPropertyHost"></param>
        /// <param name="afi"></param>
        internal CompressPropertyProvider(ICompressorWrapper wrapper, IPropertyHost rootPropertyHost, IPropertyProvider parentPropertyProvider, bool isDirectory, string fileName)
            : base(rootPropertyHost,
            isDirectory ?
           new IPropertyDefinitions[] { 
                CofeCore.PropertyDefinitions.CofeDefinitions, 
                CofeCore.PropertyDefinitions.CofeDirDefinitions, 
                CofeIO.PropertyDefinitions.FSIPropDefinitions, 
                CofeIO.PropertyDefinitions.DIPropDefinitions 
            } :
            new IPropertyDefinitions[] { 
                CofeCore.PropertyDefinitions.CofeDefinitions, 
                CofeCore.PropertyDefinitions.CofeFileDefinitions, 
                CofeCore.PropertyDefinitions.CofeStreamDefinitions,
                CofeIO.PropertyDefinitions.FSIPropDefinitions, 
                CofeIO.PropertyDefinitions.FIPropDefinitions
            
            })
        {
            _wrapper = wrapper;
            _entryType = isDirectory ? entryType.etSubFolder : entryType.etFile;
            _relativePath = fileName;
            _currentPropertyProvider = this;
            _parentPropertyProvider = parentPropertyProvider;

            if (_entryType == CompressPropertyProvider.entryType.etSubFolder)
            {
                initFolderBasedAction();
                registerActionAsync<IPropertyProvider>(CofeDirectoryProperties.CreateAsync, (pm) => createArchiveAsync(pm, true));
            }
            else
                initFileBasedAction();

            initSubitemAction();

            registerProperties(CofeProperties.DefaultInterfaceType, () =>
                isDirectory ? typeof(IDirectoryInfo) : typeof(IFileInfo));
            registerProperties(CofeProperties.Type, () => isDirectory ? FileTypes.File : FileTypes.Folder);
            registerProperties(CofeProperties.Label, () => PathFE.GetFileName(fileName));
            registerProperties(CofeProperties.IsExists, () => true);

            //registerProperties(FileSystemInfoProperties.FileAttributes, () => (Cofe.IO.FileAttributes)(int)Entity.Attributes);
            registerProperties(CofeProperties.CofeAttributes, () =>
            {
                return (isDirectory) ?
                    CofeAttributes.FileSystem | CofeAttributes.SubEntriesCanChange | CofeAttributes.Browsable :
                    CofeAttributes.FileSystem;
            }
            );

            registerProperties(CofeProperties.Type, () => isDirectory ? FileTypes.Folder : FileTypes.File);
            registerProperties(CofeProperties.ParentPropertyProvider, () => _parentPropertyProvider);
            registerProperties(CofeProperties.FullParseName, () =>
                PathFE.Combine(Entity.Behaviors.GetProperty(CofeProperties.FullParseName).ValueAsString, fileName));
            //registerProperties(CofeProperties.Name, () => PathFE.GetFileName(afi.FileName));
        }


        #endregion

        #region Methods

        private void initSubitemAction()
        {
            if (Entity.SupportedDefinitions.IsPropertySupported(FileSystemInfoProperties.FullName))
            {
                registerActionAsync(CofeProperties.DeleteAsync, (pm) => deleteAsync(pm));
            }
        }

        private void initFolderBasedAction()
        {
            Func<ParameterDic, IEnumerable<IPropertyProvider>> syncList = (pmDic) => { pmDic.IsHandled = true; return list(); };
            registerAction(CofeDirectoryProperties.ListCore, syncList);
        }

        private void initFileBasedAction()
        {
            registerActionAsync<StreamContainer>(CofeStreamProperties.OpenReadAsync, (pm) => openFileStreamAsync(true, false));

            //SevenZipSharp crash when compressor.CompressStreamDictionary() is used, 
            //if Compression mode is Append (Create is fine).
            if (Entity.Behaviors.IsPropertySupported(FileSystemInfoProperties.FullName))
            {
                registerActionAsync<StreamContainer>(CofeStreamProperties.OpenWriteAsync, (pm) => openFileStreamAsync(false, true));
                registerActionAsync<StreamContainer>(CofeStreamProperties.OpenReadWriteAsync, (pm) => openFileStreamAsync(true, true));
            }
        }

        private Task<StreamContainer> openFileStreamAsync(bool read, bool write)
        {
            HookableMemoryStream ms = new HookableMemoryStream();

            FileAccess access = FileAccess.Read;
            if (read && write)
                access = FileAccess.ReadWrite;
            else if (!read && write)
                access = FileAccess.Write;


            StreamContainer retVal = StreamContainer.FromIOStream(
                new CompressMemoryStream(_wrapper, Entity, _relativePath, access), _relativePath);
            return Task.FromResult<StreamContainer>(retVal);
        }


        //private StreamContainer openStream(bool read, bool write)
        //{
        //    return Task.Run<StreamContainer>(async () => await Entity.OpenStreamAsync(read, write)).Result;
        //}

        //private StreamContainer openRead() { return openStream(true, false); }
        //private StreamContainer openWrite() { return openStream(false, true); }
        //private StreamContainer openReadWrite() { return openStream(true, true); }


        private IEnumerable<IPropertyProvider> listVirtualItems()
        {
            Regex regex = new Regex(String.Format(RegexPatterns.CompressionListPattern, Regex.Escape(entityParseName)));
            foreach (var item in _virtualItems)
            {
                if (item.Key.StartsWith(_relativePath))
                {
                    string path = item.Key.Substring(_relativePath.Length);
                    var match = regex.Match(path);

                    if (match.Success && String.IsNullOrEmpty(match.Groups["trail"].Value))
                    {
                        yield return item.Value;
                    }
                }
            }
        }

        private IEnumerable<IPropertyProvider> list()
        {                        
            
            string listPattern = String.Format(RegexPatterns.CompressionListPattern, Regex.Escape(_relativePath));
            using (var streamContainer = Entity.OpenStream(true, false))
            {
                return (from ppList in _wrapper.List(streamContainer, listPattern, false)
                        select new CompressPropertyProvider(_wrapper, Entity, _currentPropertyProvider, ppList))
                        .Union(listVirtualItems())
                        .Distinct(EntryEqualityComparer.CompareParsePath)
                        .ToArray();
            }
        }

        private Task deleteAsync(ParameterDic dic)
        {
            using (var streamContainer = Entity.OpenStream(true, true))
            {
                string fullParseName = PathFE.Combine(Entity.Behaviors.GetProperty<string>(CofeProperties.FullParseName), _relativePath);

                lock (_virtualItems)
                    foreach (var vi in _virtualItems.ToArray())
                    {
                        if (vi.Key.StartsWith(fullParseName))
                        {
                            dic.IsHandled = true;
                            _virtualItems.Remove(vi.Key);
                        }
                    }

                if (this._entryType == entryType.etFile)
                    dic.IsHandled |= _wrapper.Delete(streamContainer, _relativePath);
                else dic.IsHandled |= _wrapper.Delete(streamContainer, PathFE.Combine(_relativePath, "*"));
            }

            return Task.Run(() => { });
        }

        private async Task<IPropertyProvider> createArchiveAsync(ParameterDic dic, bool canCreateFileOrFolder)
        {
            IPropertyProvider retVal = null;
            string type = dic.ContainsKey("Type") ? (string)dic["Type"] : null;
            string name = dic.ContainsKey("Name") ? (string)dic["Name"] : null;
            string fullRelativeName = PathFE.Combine(_relativePath, name);

            var archiveBytes = _wrapper.GetEmptyArchiveBytes(type.ToLower());
            if (archiveBytes != null)
            {
                string parseName = Entity.Behaviors.GetProperty<string>(CofeProperties.FullParseName);
                string archivePath = PathFE.Combine(parseName, name);

                using (StreamContainer container = await FileFE.CreateAsync(archivePath))
                using (BinaryWriter newFile = new BinaryWriter(container.Stream))
                    newFile.Write(archiveBytes);

                retVal = (await FileFE.FromFilePathAsync(archivePath, EntryConstructionFlags.DefaultWithoutCacheWithLookup))
                    .Properties.PrimaryPropertyProvider;
                dic.IsHandled = retVal != null;
            }

            if (!(dic.IsHandled) && canCreateFileOrFolder)
                return await createAsync(dic);

            else return retVal;
        }


        private Task<IPropertyProvider> createAsync(ParameterDic dic)
        {
            IPropertyProvider retVal = null;
            string type = dic.ContainsKey("Type") ? (string)dic["Type"] : null;
            string name = dic.ContainsKey("Name") ? (string)dic["Name"] : null;
            string fullRelativeName = PathFE.Combine(_relativePath, name);
            string fullParseName = PathFE.Combine(entityParseName, fullRelativeName);

            switch (type.ToLower())
            {
                case "folder":
                case "directory":
                    lock (_virtualItems)
                    {

                        if (!(_virtualItems.ContainsKey(fullParseName)))
                            _virtualItems.Add(fullParseName,
                                new CompressPropertyProvider(_wrapper, Entity, _currentPropertyProvider, true, fullRelativeName));
                        retVal = _virtualItems[fullParseName];
                    }
                    dic.IsHandled = true;
                    break;
                case "file":
                    lock (_virtualItems)
                    {
                        if (!(_virtualItems.ContainsKey(fullParseName)))
                            _virtualItems.Add(fullParseName,
                                new CompressPropertyProvider(_wrapper, Entity, _currentPropertyProvider, false, fullRelativeName));
                        retVal = _virtualItems[fullParseName];
                    }
                    dic.IsHandled = true;
                    break;
            }

            return Task.FromResult<IPropertyProvider>(retVal);
        }

        #endregion

        #region Data

        private static Dictionary<string, CompressPropertyProvider> _virtualItems = new Dictionary<string, CompressPropertyProvider>();
        private ICompressorWrapper _wrapper = null;
        private string _relativePath = "";
        private IPropertyProvider _parentPropertyProvider = null;
        private IPropertyProvider _currentPropertyProvider = null;
        private entryType _entryType;
        //private 

        #endregion

        #region Public Properties

        private string entityParseName { get { return Entity.Behaviors.GetProperty<string>(CofeProperties.FullParseName); } }

        #endregion

    }
}
